﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using AGB.IDemonbuddy;
using AGB.Misc;
using AGB.Modules.FrontalLobe_.VendorRun;
using Win32;
using Zeta;
using Zeta.Common;
using Zeta.CommonBot;
using Zeta.Internals;
using Zeta.Internals.Actors;
using Zeta.Internals.SNO;

namespace AGB.Managerial
{
    public class UnitManager
    {
        public static int EntityCounter = 0;
        public event UnitCastingHandler UnitCasting;

        public delegate void UnitCastingHandler(DiaUnit unit);

        private bool _running = false;
        private readonly Thread _updateThread;
        private Dictionary<int, CachedItem> Items;
        private Dictionary<int, CachedGizmo> Gizmos;
        private Dictionary<int, CachedUnit> Units;
        private Dictionary<int, CachedUnit> _oldUnits;
        private Dictionary<int, CachedGizmo> _oldGizmos;
        private List<DiaObject> _cache; 
        public static Vector3 Me = new Vector3(0, 0, 0);
        public Action myTick;
        public Object UnitManagerLock = new object();
        private bool _clearCommand = false;
        private long _lastTick = Environment.TickCount;
        private long _tickDelay = 200;
        private int _currentIterationCacheCount = 0;

        public UnitManager()
            : base()
        {
            //GameEvents.OnItemLooted += OnItemLoot;
            Logging.Write("Instanciated UnitManager");
            Items = new Dictionary<int, CachedItem>();
            Gizmos = new Dictionary<int, CachedGizmo>();
            Units = new Dictionary<int, CachedUnit>();
            _oldUnits = new Dictionary<int, CachedUnit>();
            _oldGizmos = new Dictionary<int, CachedGizmo>();
            _cache = new List<DiaObject>();
            myTick = new Action(Tick);
        }

        public static void OnItemLoot(object sender, ItemLootedEventArgs args)
        {
            //Logging.Write(args.Item.Name+" looted");
        }

        private static int team1 = -255;


        public bool IsTeamHostile(int team2)
        {
            return team1 != team2
                && team1 != 1
                && team2 != 1
                && (team1 != 14 || (uint)(team2 - 10) > 3)
                && (team2 != 14 || (uint)(team1 - 10) > 3)
                && ((uint)(team1 - 15) > 7 || (uint)(team2 - 2) > 7)
                && ((uint)(team1 - 2) > 7 || (uint)(team2 - 15) > 7);
        }

        /*
        ///ASM Magic for later maybe
        private int GetAttributeAsInteger(ActorAttributeType attribute)
        {
            var instance = ZetaDia.Memory.
            
            if (instance != null)
            {
                lock (instance.)
                {
                    instance.AddRandomLine("push " + (int)attribute);
                    instance.AddRandomLine("mov ecx [{0}]", BaseAddress);
                    instance.AddRandomLine("call 0x000B46B0");
                    instance.AddRandomLine("retn");
                    instance.Execute();
                    return ZetaDia.Memory.Read<int>(instance.ReturnPointer);
                }
            }
        }*/


        public T GetObject<T>(String name = "", int ActorSNO = -1) where T : CachedObject
        {
            if (new CachedUnit(null) is T)
            {
                if (ActorSNO > 0)
                {
                    var nGizmo = Units.Values.FirstOrDefault(unit => unit.ActorSNO == ActorSNO);
                    if (nGizmo == default(CachedUnit)) return null;
                    return nGizmo as T;
                }
                var nGizmo2 = Units.Values.FirstOrDefault(unit => unit.Name.Contains(name));
                if (nGizmo2 == default(CachedUnit)) return null;
                return nGizmo2 as T;
            }

            if (new CachedGizmo(null) is T)
            {
                if (ActorSNO > 0)
                {
                    var nGizmo = Gizmos.Values.FirstOrDefault(unit => unit.ActorSNO == ActorSNO);
                    if (nGizmo == default(CachedGizmo)) return null;
                    return nGizmo as T;
                }
                var nGizmo2 = Gizmos.Values.FirstOrDefault(unit => unit.Name.Contains(name));
                if (nGizmo2 == default(CachedGizmo)) return null;
                return nGizmo2 as T;

            }

            return null;
        }

        public PriorityQueue<float, CachedUnit> GetHostiles()
        {
            var Queue = new PriorityQueue<float, CachedUnit>();
            foreach (var cachedUnit in Units.Values.Where(unit => unit.Hostile))
            {
                Queue.Enqueue(cachedUnit.getDistance(), cachedUnit);
            }
            return Queue;
        }



        public CachedUnit GetClosestHostile()
        {
            /*float minDist = float.MaxValue;
            CachedUnit bestUnit = null;
            foreach (var cachedUnit in Units.Where(unit => unit.Value.Hostile))
            {
                if (cachedUnit.Value.getDistance() < minDist)
                {
                    bestUnit = cachedUnit.Value;
                    minDist = bestUnit.getDistance();
                }
            }*/
            var host = GetHostiles();
            if (host.Count < 1) return null;
            return GetHostiles().DequeueValue();
        }

        public bool EvaluateItem(DiaItem item)
        {
            if (item.CommonData == null) return false;
            return ItemManager.EvaluateItem(item.CommonData, ItemManager.RuleType.PickUp);
        }

        public CachedItem GetClosestMagicItem()
        {
            lock (Units)
            {

                float minDist = float.MaxValue;
                DiaItem bestUnit = null;

                foreach (var cachedUnit in ZetaDia.Actors.GetActorsOfType<DiaItem>().Where(EvaluateItem))
                {
                    if (cachedUnit.CommonData.Gold > 1 && AGB.Properties.Settings.Default.FerretLoot) continue;
                    if (cachedUnit.Distance < minDist)
                    {
                        bestUnit = cachedUnit;
                        minDist = bestUnit.Distance;
                    }
                }
                if (bestUnit == null) return null;
                return new CachedItem(bestUnit);
            }
        }


        private void CacheUnit(DiaUnit du)
        {
            int guid = du.ACDGuid;
            if (!Units.ContainsKey(guid))
            {
                CachedUnit cu = new CachedUnit(du);
                if (!cu.CachingFailed)
                {
                    _currentIterationCacheCount++;
                    Units.Add(du.ACDGuid, cu);
                }
            }
        }

        private void CacheItem(DiaItem di)
        {
            int guid = di.ACDGuid;
            if (!Items.ContainsKey(guid))
            {
                CachedItem ci = new CachedItem(di);
                if (!ci.CachingFailed)
                {
                    Items.Add(guid, ci);
                }
            }
        }

        private void CacheGizmo(DiaGizmo dg)
        {
            int guid = dg.ACDGuid;
            if (!Gizmos.ContainsKey(guid))
            {
                CachedGizmo cg = new CachedGizmo(dg);
                if (!cg.CachingFailed)
                {
                    _currentIterationCacheCount++;
                    Gizmos.Add(guid, cg);
                }
            }
        }

        private void ValidateUnits()
        {
            Dictionary<int, CachedUnit> UnitStore = new Dictionary<int, CachedUnit>();

            foreach (var cachedUnit in Units.Values)
            {
                if (cachedUnit.IsValid())
                    UnitStore.Add(cachedUnit.ACDGuid, cachedUnit);

            }

            /*_oldUnits.Clear();
            _oldUnits = null;
            _oldUnits = Units;
            Units.Clear();*/
            Units = UnitStore;
        }

        private void ValidateGizmos()
        {
            Dictionary<int, CachedGizmo> GizmoStore = new Dictionary<int, CachedGizmo>();


            foreach (var cachedGizmo in Gizmos.Values)
            {
                if (cachedGizmo.IsValid())
                    GizmoStore.Add(cachedGizmo.ACDGuid, cachedGizmo);

            }

            //EntityCounter -= _oldGizmos.Count;

            /*_oldGizmos.Clear();
            _oldGizmos = null;
            _oldGizmos = Gizmos;
            Gizmos.Clear();*/
            Gizmos = GizmoStore;
        }

        private void ValidateItems()
        {
            Dictionary<int, CachedItem> ItemStore = new Dictionary<int, CachedItem>();

            foreach (var cachedItem in Items.Values)
            {
                if (cachedItem.IsValid())
                    ItemStore.Add(cachedItem.ACDGuid, cachedItem);

            }
            Items = ItemStore;
        }

        public void Clear()
        {
            Units.Clear();
            Items.Clear();
            Gizmos.Clear();
        }

        public void mCallback(long time)
        {
            Logging.Write("Time: "+time);
        }

        public void Tick()
        {

            int a = Environment.TickCount;
            //ItemManager.Refresh();
            if (_clearCommand)
            {
                _clearCommand = false;
                Units.Clear();
                Gizmos.Clear();
            }

            lock (BotManager.UnitManager)
            {
                //var x = new HiPerfTimer();
                //x.Start();

                if (_lastTick + _tickDelay < Environment.TickCount)
                {
                    _lastTick = Environment.TickCount;
                    if (team1 == -255)
                        team1 = ZetaDia.Actors.Me.CommonData.GetAttribute<int>(ActorAttributeType.TeamID);

                    if (_cache == null) _cache = ZetaDia.Actors.GetActorsOfType<DiaObject>(true, true); else
                    {
                    }

                    foreach (var NativeObject in _cache)
                    {
                        if (NativeObject is DiaActivePlayer)
                        {
                            var z = NativeObject as DiaActivePlayer;
                            ZetaWrap.InventorySlots = ZetaDia.Me.Inventory.NumFreeBackpackSlots;

                        }
                        if (NativeObject is DiaUnit) CacheUnit(NativeObject as DiaUnit);
                        else if (NativeObject is DiaGizmo) CacheGizmo(NativeObject as DiaGizmo);

                        //else if (NativeObject is DiaItem) CacheItem(NativeObject as DiaItem);
                        /*if (_currentIterationCacheCount >= 5)
                        {
                            ValidateUnits();
                            ValidateGizmos();
                            return;
                        }*/
                    }
                }
                //x.Stop();
                //Logging.Write("time: " + x.Duration);
            }

            _currentIterationCacheCount = 0;
            ValidateUnits();
            ValidateGizmos();
            _cache = null;


            /*var inventory = ZetaDia.Me.Inventory;
            if (inventory == null) return;

                                        x.Stop();
                            Logging.Write("time: "+x.Duration);
            ZetaWrap.InventorySlots = ZetaDia.Me.Inventory.NumFreeBackpackSlots;*/

        }

        public bool ItemIsTwoSlot(ACDItem item)
        {
            bool two = false;
            var bType = item.ItemBaseType;
            if (bType != ItemBaseType.Armor && bType != ItemBaseType.Weapon) return false;
            var type = item.ItemType;
            if (type == ItemType.Belt) return false;
            return true;
        }

        //We still need to delta, Demonbuddy will not give us events.
        /// <summary>
        /// Caching, Eventing all that shit !
        /// </summary>
        public void Pulse()
        {
            //Thread.Sleep(100);
            try
            {
                Tick();
            }
            catch (Exception e)
            {
                Logging.WriteException(e);
            }
        }
    }
}
